package core

import (
	"container/heap"
	"sync"
	"sync/atomic"
	"time"
)

// ============================================================================
// TimerQueue
// ============================================================================

type Timer struct {
	ts    time.Time // expire timestamp
	f     func()    // timer callback
	index int       // heap index
	valid bool      // valid timer?
}

func (self *Timer) Time() time.Time {
	return self.ts
}

// ============================================================================

type timer_array_t []*Timer

func (self timer_array_t) Len() int {
	return len(self)
}

func (self timer_array_t) Less(i, j int) bool {
	return self[i].ts.Before(self[j].ts)
}

func (self timer_array_t) Swap(i, j int) {
	self[i], self[j] = self[j], self[i]
	self[i].index, self[j].index = i, j
}

func (self *timer_array_t) Push(v interface{}) {
	t := v.(*Timer)
	t.index = len(*self)
	t.valid = true
	*self = append(*self, t)
}

func (self *timer_array_t) Pop() interface{} {
	arr := *self
	n := len(arr)
	t := arr[n-1]
	t.index = -1
	t.valid = false
	*self = arr[:n-1]

	return t
}

// ============================================================================

type TimerQueue struct {
	q timer_array_t

	locker sync.Mutex
}

func NewTimerQueue() *TimerQueue {
	tq := &TimerQueue{}
	heap.Init(&tq.q)
	return tq
}

func (self *TimerQueue) SetTimeout(ts time.Time, f func()) *Timer {
	self.locker.Lock()
	defer self.locker.Unlock()

	t := &Timer{
		ts: ts,
		f:  f,
	}

	heap.Push(&self.q, t)
	return t
}

func (self *TimerQueue) Cancel(t *Timer) {
	self.locker.Lock()
	defer self.locker.Unlock()

	if t != nil && t.valid && t.index >= 0 && t.index < len(self.q) {
		heap.Remove(&self.q, t.index)
	}
}

func (self *TimerQueue) Update(t *Timer, ts time.Time) {
	self.locker.Lock()
	defer self.locker.Unlock()

	if t != nil && t.valid && t.index >= 0 && t.index < len(self.q) {
		t.ts = ts
		heap.Fix(&self.q, t.index)
	}
}

func (self *TimerQueue) Expire(now time.Time) func() {
	self.locker.Lock()
	defer self.locker.Unlock()

	if self.q.Len() == 0 {
		return nil
	}

	t := self.q[0]

	if t.ts.After(now) {
		return nil
	}

	heap.Pop(&self.q)

	return t.f
}

// ============================================================================
// default timerq
// ============================================================================

var def_timerq = NewTimerQueue()

func SetTimeout(ts time.Time, f func()) *Timer {
	return def_timerq.SetTimeout(ts, f)
}

func CancelTimer(t *Timer) {
	def_timerq.Cancel(t)
}

func init() {
	go func() {
		for {
			now := time.Now()

			for {
				f := def_timerq.Expire(now)
				if f == nil {
					break
				}

				// run in its own thread
				Go(f)
			}

			time.Sleep(200 * time.Millisecond)
		}
	}()
}

// ============================================================================
// Timerable
// ============================================================================

var seq_timer_id int64

// ============================================================================

// timerable methods MUST be called in the parent goroutine
// SetParent() shall be called at creation phase

type Timerable struct {
	timers map[int64]*timer_id_t
	parent ITimerableParent
}

type timer_id_t struct {
	id  int64
	tid *Timer
}

type ITimerableParent interface {
	Push(f func()) // used to push timeout func to parent goroutine
}

// ============================================================================

func (self *Timerable) SetParent(parent ITimerableParent) {
	self.parent = parent
}

func (self *Timerable) SetTimeout(ts time.Time, f func()) int64 {
	// do NOT set if no parent or parent is gone
	if self.parent == nil {
		return 0
	}

	// create timer object
	t := &timer_id_t{
		id: atomic.AddInt64(&seq_timer_id, 1),
	}

	// add timer object
	if self.timers == nil {
		self.timers = make(map[int64]*timer_id_t)
	}

	self.timers[t.id] = t

	// set timer
	t.tid = SetTimeout(ts, func() {
		// callback in parent goroutine
		parent := self.parent
		if parent != nil {
			parent.Push(func() {
				// callback if the timer still exists (not cancelled)
				if self.timers[t.id] != nil {
					delete(self.timers, t.id)
					f()
				}
			})
		}
	})

	// return timer id
	return t.id
}

func (self *Timerable) CancelTimer(id int64) {
	// find timer object
	t := self.timers[id]
	if t == nil {
		return
	}

	// cancel
	CancelTimer(t.tid)

	// remove timer object
	delete(self.timers, id)
}

func (self *Timerable) CancelAllTimers() {
	// cancel all
	for _, t := range self.timers {
		CancelTimer(t.tid)
	}

	// clear timer objects
	self.timers = nil
}
